#ifndef ARRAYLIST_HPP
#define	ARRAYLIST_HPP

#include <stdexcept>
#include <assert.h>

namespace OwnCollections {

    template<class T>
    class ArrayList {
    private:
        /**
         * Начальная емкость хранилища
         */
        static const int INITIAL_CAPACITY = 4;

        /*
         * Нижний предел емкости хранилища
         * (при его достижении размер коллекции
         * не уменьшается)
         */
        static const int LOWER_CAPACITY_THRESHOLD = 4;

        /**
         * Во сколько раз увеличивать (или уменьшать)
         * емкость хранилица при перераспределении памяти
         */
        static const int CAPACITY_RESIZE_FACTOR = 2;

        /**
         * Память дл яхранения элементов коллекции
         */
        T *storage;

        /**
         * Текущее количество элементов
         */
        int count;

        /**
         * Максимальная емкость коллекции
         * (сколько элементов может в ней
         * содержаться без перераспределения памяти)
         */
        int capacity;

    public:

        ArrayList(int initialCapacity = INITIAL_CAPACITY) {
            if( initialCapacity < 1 ) {
                throw std::invalid_argument("capacity must be >= 1");
            }

            storage = new T[initialCapacity];
            capacity = initialCapacity;
            count = 0;
        }
        
        ArrayList(const ArrayList& orig) {
            assert( orig.count >= 0 );
            assert( orig.capacity >= 0 );
            count = orig.count;
            
            capacity = orig.capacity;
            storage = new T[capacity];
            copy( storage, orig.storage, count );
            
        }

        ArrayList& operator = (const ArrayList& orig) {
            if( this != orig ) {
                delete[] storage;
                this(orig);
            }
            
            return this;
        }
        /**
         * Добавляет элемент в конец списка
         * @param addObj Добавляемый объект
         */
        void add(const T& addObj) {
            if (needIncreaseCapacityToAddItem()) {
                increaseCapacity();
            }
            storage[count] = addObj;
            count++;
        }

        T& get(int index) const {
            if (!isIndexCorrect(index)) {
                throw std::out_of_range("invalid index");
            }

            return storage[index];
        }

        void set(int index, const T& obj) {
            assert( count >= 0 );
            if (!isIndexCorrect(index)) {
                throw std::out_of_range("invalid index");
            }
            storage[index] = obj;
        }

        /**
         * Удаляет элемент из конца списка
         * @return элемент в конце списка
         */
        T& remove() {
            assert(count >= 0);
            if (count == 0) {
                throw std::out_of_range("cant remove from empty list");
            }
            T& element = storage[count - 1];
            count--;
            if (needDecreaseCapacityToRemoveItem()) {
                decreaseCapacity();
            }

            return element;
        }

        /**
         *
         * @return Текущее количество
         * элементов в коллекции
         */
        int getCount() const {
            assert(count >= 0);
            return count;
        }

        /**
         * Увеличивает размер хранилища на
         * заданную величину
         */
        void expand(int expandSize) {
            if( expandSize < 0 ) {
                throw std::out_of_range("expand size must be >= 0");
            }

            if( 0 == expandSize ) {
                return;
            }

            int newCapacitySize = capacity + expandSize;
            increaseCapacity(newCapacitySize);
        }

        const T& operator [] (const int index) const {
            return get(index);
        }

        T& operator [] (const int index) {
            return get(index);
        }

        void swap( int firstElementIndex, int secondElementIndex ) {
           if (!isIndexCorrect(firstElementIndex)) {
                throw std::out_of_range("invalid first index");
            }

           if (!isIndexCorrect(secondElementIndex)) {
                throw std::out_of_range("invalid second index");
            }

           T temp = storage[firstElementIndex];
           storage[firstElementIndex] = storage[secondElementIndex];
           storage[secondElementIndex] = temp;
        }

    private:

        bool needIncreaseCapacityToAddItem() {
            assert(count <= capacity);
            if (count == capacity) {
                return true;
            }
            return false;
        }

        void increaseCapacity() {
            int newCapacitySize = capacity * CAPACITY_RESIZE_FACTOR;
            increaseCapacity(newCapacitySize);
        }

        void increaseCapacity(int newCapacitySize) {
            assert( newCapacitySize > capacity );
            T *increasedStorage = new T[newCapacitySize];
            copy(increasedStorage, storage, count);
            delete[] storage;
            storage = increasedStorage;
            capacity = newCapacitySize;
        }

        bool needDecreaseCapacityToRemoveItem() {
            int thresholdCapacity = capacity / CAPACITY_RESIZE_FACTOR;
            assert(count >= 0);

            if (count == thresholdCapacity &&
                    count > LOWER_CAPACITY_THRESHOLD) {
                return true;
            }

            return false;
        }

        void decreaseCapacity() {
            int thresholdCapacity = capacity / CAPACITY_RESIZE_FACTOR;
            assert(count == thresholdCapacity);
           
            T* decreaseStorage = new T[thresholdCapacity];
            copy(decreaseStorage, storage, count);
            delete[] storage;
            storage = decreaseStorage;
        }

        void copy(T *destination, T *source, int count) {
            assert(NULL != destination);
            assert(NULL != source);
            for (int i = 0; i < count; i++) {
                destination[i] = source[i];
            }
        }

        bool isIndexCorrect(int index) const {
            if (index < 0 || index > count - 1) {
                return false;
            }

            return true;
        }
    };
}

#endif

